/******************************************************************************
 * $Id: widgets.c,v 1.179 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Other widget and misc GTK+ related functions.
 ******************************************************************************/

#include "common.h"
#include "widgets.h"

#include "notebook.h"
#include "list.h"
#include "text.h"
#include "history.h"
#include "treefunc.h"
#include "menu.h"
#include "statusbar.h"
#include "toolbar.h"
#include "focusbar.h"
#include "handlers.h"
#include "handlers_tree.h"

#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>

/*
 * Set the main window's title bar.
 */
void
SetMainWindowTitle()
{
	gchar * title = NULL;
	module_t * pModule = GetModuleTextMain();//ActiveModule();
	if ( pModule)
	{
		if ( TRUE == pModule->modified )
			title = g_strdup_printf(_("Hypersrc < *%s >"),pModule->pModuleName);
		else
			title = g_strdup_printf(_("Hypersrc < %s >"),pModule->pModuleName);
	}
	else
	{
		title = g_strdup_printf(_("Hypersrc < *New document >"));
	}

	gtk_window_set_title(GTK_WINDOW(pWidgetMain), title);
	g_free(title);
	title = NULL;
	return;
}

/******************************************************************************
 * Create all of the widgets.
 *
 * This was bifurcated into two steps so that the GUI starts sooner
 * (without irritatingly popping up after several minutes of loafing.)
 * The second step is called after tagging/graphing has completed.
 *
 * @param   argc, argv
 *          the set of pWidgets* (OUT/GLOBAL)
 ******************************************************************************/
void
CreateWidgetsStep1( void )
{
  /*
   * Create the main window.
   */
   CreateMainWindow();
   if ( mainWindowTitle[0] )
      SetMainWindowTitle();
  /*
   * In order to gracefully exit if stopped by user during startup.
   */
   g_signal_connect( G_OBJECT(pWidgetMain),
                       "delete-event",
                       G_CALLBACK(HandlerDeleteEvent),
                       NULL);

  /*
   * Create a vbox and attach it to the main window.
   */
   pWidgetVboxMain = gtk_vbox_new( FALSE, 0 );
   gtk_container_add( GTK_CONTAINER(pWidgetMain),
                      pWidgetVboxMain );

  /*
   * Create the menubar/toolbar.
   */
   CreateMenubarToolbar();

   SetSensitivityWhileBusy( FALSE );

  /*
   * Create the tags clist widget.
   */
   CreateTagsClistWidget();

  /*
   * Create the modules clist widget.
   */
   CreateModulesClistWidget();

  /*
   * Create the history clist widget.
   */
   CreateHistoryClistWidget();

  /*
   * Create the notebook and function tree widgets
   * (to be added to the left half of the paned widget).
   */
   CreateNotebook();
   if ( ! skipFuncTree )
   {
      SelectDifferentFontsForFuncTrees( fontNameTree );
      UpdateFuncTreeWidget( NULL );
   }

  /*
   * Create the text widget et al (to be added to the right half of the paned widget).
   */
   CreateTextWidgetEtAl( &pWidgetTextMain,
                         &pWidgetVboxTextMain,
                         &pWidgetTableTextMain,
                         &pWidgetVscrollTextMain,
                         &pWidgetHscrollTextMain,
                         &pFocusbarTextMain );
   CreateTextWidgetEtAl( &pWidgetTextAux,
                         &pWidgetVboxTextAux,
                         &pWidgetTableTextAux,
                         &pWidgetVscrollTextAux,
                         &pWidgetHscrollTextAux,
                         &pFocusbarTextAux );

   
   /**/
   GtkWidget *pVboxTextAuxStatusbar=NULL;
   pVboxTextAuxStatusbar = gtk_vbox_new(FALSE, 0);
   gtk_box_pack_start(GTK_BOX(pVboxTextAuxStatusbar), pWidgetVboxTextAux,TRUE, TRUE, 1);

  /*
   * Pack the notebook and function tree widget into a vertical sub-pane.
   */
   if ( ! skipFuncTree )
   {
      pWidgetPanedFuncTree = gtk_hpaned_new();

      gtk_paned_add1 ( GTK_PANED(pWidgetPanedFuncTree), pVboxTextAuxStatusbar );
      gtk_paned_add2( GTK_PANED(pWidgetPanedFuncTree), pWidgetFrameFuncTree );
   }

  /*
   * Set main text widget as the active one.
   */
   SetActiveTextWidget( pWidgetTextMain );

  /*
   * Pack the two text widgets into a vertical pane.
   */
   pWidgetPanedText = gtk_hpaned_new();
   
  /*
   * Create the paned widget (which contains the notebook and text widgets).
   */
   pWidgetPaned = gtk_vpaned_new();
   gtk_paned_add1(GTK_PANED(pWidgetPaned), pWidgetPanedText);

   if ( ! skipFuncTree )
   		gtk_paned_add2(GTK_PANED(pWidgetPaned), pWidgetPanedFuncTree);
   else
   		gtk_paned_add2(GTK_PANED(pWidgetPaned), pVboxTextAuxStatusbar);
   
   gtk_box_pack_start( GTK_BOX(pWidgetVboxMain),
                       pWidgetPaned,
                       TRUE, TRUE, 1 );
    
   textLeftApplied = textLeft; /* remember how layout was applied */
   if ( textLeft == FALSE )
   {
      gtk_paned_add1( GTK_PANED(pWidgetPanedText), pWidgetNotebook );
      gtk_paned_add2( GTK_PANED(pWidgetPanedText), pWidgetVboxTextMain );
   }
   else
   {
      gtk_paned_add2( GTK_PANED(pWidgetPanedText), pWidgetNotebook );
      gtk_paned_add1( GTK_PANED(pWidgetPanedText), pWidgetVboxTextMain );
   }
   
   gtk_widget_set_usize( GTK_WIDGET(pWidgetPaned),
                         PANED_WIDTH_HORZ,
                         PANED_WIDTH_VERT );

  /*
   * Create the statusbar.
   */
   pWidgetStatusbar = gtk_statusbar_new();
   gtk_box_pack_end( GTK_BOX(pVboxTextAuxStatusbar),
                     pWidgetStatusbar,
                     0, 0, 0);
   statusbarContextId = gtk_statusbar_get_context_id( GTK_STATUSBAR( pWidgetStatusbar ),
                                                      "hypersrc" );
   gtk_statusbar_push( GTK_STATUSBAR( pWidgetStatusbar ),	/* PrintStatusbar() can't be used yet */
                       statusbarContextId,
                       _("   Starting, please wait...") );

  /*
   * Create notebook page to hold tree views of modules and their functions.
   * (to be decorated after funcs are graphed).
   */
   CreateNotebookPageWithModuleTree( DONT_DECORATE );

  /*
   * Show all windows.
   */
   gtk_widget_show_all( pWidgetMain );

  /*
   * Some widget operations can only be done after a widget is visible.
   */
   FocusbarLabelSet( pFocusbarTextMain, "(empty)" );
   if ( auxKeyGdkKeyval == GDK_Shift_L )
      FocusbarLabelSet( pFocusbarTextAux,  "(click here or press Left-Shift while selecting a tag to display here)" );
   else
      FocusbarLabelSet( pFocusbarTextAux,  "(click here or press Left-Ctrl while selecting a tag to display here)" );

  /*
   * Position the paned sliders.
   */
   SetPanedSlidersInitialPositions();

  /*
   * Install a key press event handler.
   */
   g_signal_connect( G_OBJECT(pWidgetMain),
                       "key-press-event",
                      G_CALLBACK(HandlerKeyPressMain),
                       NULL );
   g_signal_connect( G_OBJECT(pWidgetMain),
                       "key-release-event",
                       G_CALLBACK(HandlerKeyRelease),
                       NULL );
   gtk_widget_add_events( pWidgetMain,
                          GDK_KEY_RELEASE_MASK );	/* to enable key release events */

  /*
   * Enable a handler for updating the line #.
   */
   ConnectHandlersForTextWidget( pWidgetTextMain );
   ConnectHandlersForTextWidget( pWidgetTextAux );

  /*
   * Widgets are ready (operational), but are still insensitive
   * to user input until tags/funcgraph is complete.
   */
   widgetsReady = TRUE;

   return;
}

/*****************************************************************************
 * Step #2 of CreateWidgets().
 * Fully display widgets that depend on the now-available tag/funcgraph data.
 *****************************************************************************/
void
CreateWidgetsStep2( void )
{
  /*
   * init the tag's and file's type xpm icon resource
   */
   InitPixmaps();
    
  /*
   * Populate the tags clist widget.
   */
   PopulateTagsClistWidget( tagSortInitial );

  /*
   * Populate the modules clist widget.
   */
   PopulateModulesClistWidget();

  /*
   * Create notebook page to hold tree views of modules and their functions.
   */
   CreateNotebookPageWithModuleTree( DO_DECORATE );

  /*
   * Ready for user input.
   */
   SetSensitivityWhileBusy( TRUE );

  /*
   * Print a banner in status bar (unless the status bar has a pending msg).
   */
   gtk_statusbar_pop( GTK_STATUSBAR( pWidgetStatusbar ), /* pop "Starting, please wait..." */
                      statusbarContextId );              /* (ClearStatusbar() won't remove this one) */
   ClearStatusbar();
   SetStatusbarInitialMsg( HYPERSRC_TITLE_STRING );
   PrintStatusbar( pStatusbarInitMsg );

   /*
    * Make the main text view default
    */
   GTK_WIDGET_SET_FLAGS( pWidgetTextMain,
                         GTK_CAN_DEFAULT );
   gtk_widget_grab_default(pWidgetTextMain);
   return;
}

/******************************************************************************
 * Create the main window.
 * The main window can be created using GTK+, rather than gnome_app_new().
 * @param   pWidgetMain (OUT/GLOBAL)
 ******************************************************************************/
void
CreateMainWindow( void )
{
  /*
   * Create the main window.
   */
   pWidgetMain = gtk_window_new( GTK_WINDOW_TOPLEVEL );
   
   g_signal_connect( G_OBJECT(pWidgetMain),
                       "destroy",
                       G_CALLBACK(HandlerDeleteEvent),  //gtk_main_quit),
                       NULL );
   gtk_window_set_title( GTK_WINDOW (pWidgetMain),
                         "hypersrc" );
   InstallFocusHandlers( pWidgetMain );

  /*
   * If "-geom" was specified (indicated by widgetMainGeom[0] != -1),
   * then change the geometry of the main widget.
   */
   if ( widgetMainGeom[0] != -1 )
   {
      int y;

     /*
      * KLUDGE: The main window has a tendency to gradually descend every time
      *         the program is started after prefs were previously saved,
      *         so subtract a magic displacement from y
      *         (which seems to work fairly well on IceWM and KDE).
      */
      y = widgetMainGeom[1] - 24; /* KLUDGE: -24 is a magic displacement */
      if ( y < 0 ) y = 0;

      gtk_widget_set_uposition( GTK_WIDGET(pWidgetMain),
                                widgetMainGeom[0],
                                y );

      if (    widgetMainGeom[2] > MIN_MAIN_WIDGET_WIDTH
           && widgetMainGeom[3] > MIN_MAIN_WIDGET_HEIGTH )
      {
      #if 0
        /*
         * This forces minimum size of widget.
         */
         gtk_widget_set_usize( GTK_WIDGET(pWidgetMain),
                               widgetMainGeom[2],
                               widgetMainGeom[3] );
      #else
         gtk_window_set_default_size( GTK_WINDOW(pWidgetMain),
                                      widgetMainGeom[2],
                                      widgetMainGeom[3] );
      #endif
      }
   }
   // install mainwindow image

   GdkPixbuf *icon;
   icon = gdk_pixbuf_new_from_file(
			IMAGE_DIR"hypersrc.png",NULL);
   gtk_window_set_icon(GTK_WINDOW(pWidgetMain), icon);
   gtk_window_set_default_icon(icon);
   
   if (icon)
	   g_object_unref(icon);

   return;
}

/******************************************************************************
 * Create the menubar/toolbar within a framed hbox, then attach to main vbox.
 ******************************************************************************/
void
CreateMenubarToolbar( void )
{
   GtkWidget*	pWidgetHboxMenubarToolbar;
   GtkWidget*	pWidgetFrameMenubarToolbar;

  /*
   * Create the menubar and toolbar within their own hbox.
   */
   if ( toolbarPlacement == TOOLBAR_PLACEMENT_RIGHT )
   {
      pWidgetHboxMenubarToolbar = gtk_hbox_new( FALSE, 0 );
   }
   else /* toolbarPlacement == TOOLBAR_PLACEMENT_BELOW */
   {
     /*
      * Really call vbox to place the toolbar below the menubar.
      */
      pWidgetHboxMenubarToolbar = gtk_vbox_new( FALSE, 0 );
   }
   CreateMenubar( pWidgetHboxMenubarToolbar );
//   CreateToolbar( pWidgetHboxMenubarToolbar );

   //store Menubar, Toolbar, Toolbar2
   GtkWidget*   pWidgetHboxMenubarToolbar2;
   
   
   pWidgetHboxMenubarToolbar2 = gtk_hbox_new(FALSE, 0); //gtk_vbox_new(FALSE, 0);

   gtk_box_pack_start(GTK_BOX(pWidgetHboxMenubarToolbar2),
                                  pWidgetHboxMenubarToolbar,
                                  FALSE, FALSE, 0);

   // create the second toolbar
   CreateToolbar2(pWidgetHboxMenubarToolbar2);
   CreateToolbar (pWidgetHboxMenubarToolbar2);
#if 0
  /*
   * Frame the menu/toolbar hbox.
   */
   pWidgetFrameMenubarToolbar = gtk_frame_new( NULL );
   gtk_frame_set_label_align( GTK_FRAME(pWidgetFrameMenubarToolbar),
                              0.10, 0 );
   gtk_frame_set_shadow_type( GTK_FRAME(pWidgetFrameMenubarToolbar),
                              GTK_SHADOW_OUT );

   gtk_container_add( GTK_CONTAINER(pWidgetFrameMenubarToolbar),
                      pWidgetHboxMenubarToolbar2 );
#endif
  /*
   * Pack the menu/toolbar vbox into the main hbox.
   */
   gtk_box_pack_start( GTK_BOX(pWidgetVboxMain),
                       pWidgetHboxMenubarToolbar2,	//pWidgetFrameMenubarToolbar,
                       FALSE, FALSE, 0 );

   return;
}

/******************************************************************************
 * Change the text/selection colors of a widget.
 ******************************************************************************/
void
ChangeStyleColorTextSel( GtkWidget* pWidget,
                         GdkColor*  pColorFgText,
                         GdkColor*  pColorBgText,
                         GdkColor*  pColorFgSel,
                         GdkColor*  pColorBgSel )
{
   GtkStyle*	pStyle;

  /*
   * Change the color of the widget indirectly
   * by changing the color in the style struct.
   */
   pStyle = gtk_widget_get_style( pWidget );
   pStyle = gtk_style_copy( pStyle );

   pStyle->fg[GTK_STATE_NORMAL]   = *pColorFgText;	/* applies to clist fg */
   pStyle->text[GTK_STATE_NORMAL] = *pColorFgText;	/* applies to text  fg */
   pStyle->base[GTK_STATE_NORMAL] = *pColorBgText;

   pStyle->fg[GTK_STATE_SELECTED] = *pColorFgSel;
   pStyle->bg[GTK_STATE_SELECTED] = *pColorBgSel;

   gtk_widget_set_style( pWidget, pStyle );

   return;
}

/******************************************************************************
 * Change the color of text of a widget.
 ******************************************************************************/
void
ChangeStyleColorText( GtkWidget* pWidget,
                      GdkColor*  pColorFg,
                      GdkColor*  pColorBg )
{
   GtkStyle*	pStyle;

  /*
   * Change the color of the widget indirectly
   * by changing the color in the style struct.
   */
   pStyle = gtk_widget_get_style( pWidget );
   pStyle = gtk_style_copy( pStyle );

   pStyle->fg[GTK_STATE_NORMAL]   = *pColorFg;	/* applies to clist fg */
   pStyle->text[GTK_STATE_NORMAL] = *pColorFg;	/* applies to text  fg */
   pStyle->base[GTK_STATE_NORMAL] = *pColorBg;

   gtk_widget_set_style( pWidget, pStyle );

   return;
}

/******************************************************************************
 * Change the color of a selection of a widget.
 ******************************************************************************/
void
ChangeStyleColorSel( GtkWidget* pWidget,
                     GdkColor*  pColorFg,
                     GdkColor*  pColorBg )
{
   GtkStyle*	pStyle;

  /*
   * Change the color of the widget indirectly
   * by changing the color in the style struct.
   */
   pStyle = gtk_widget_get_style( pWidget );
   pStyle = gtk_style_copy( pStyle );

   pStyle->fg[GTK_STATE_SELECTED] = *pColorFg;
   pStyle->bg[GTK_STATE_SELECTED] = *pColorBg;

   gtk_widget_set_style( pWidget, pStyle );

   return;
}

/******************************************************************************
 * Change the font of a widget.
 * @param   pWidget
 * @param   pFontName
 *          If "" or NULL then does nothing.
 * @return  TRUE if successful.
 ******************************************************************************/
gboolean
ChangeStyleFont( GtkWidget* pWidget,
                 char*      pFontName )
{
   GtkStyle*	pStyle;

   PangoFontDescription * font_desc=NULL;
  /*
   * Abort if the widget or font name is nothing.
   */
   if ( !pWidget  ||  IsStringEmpty(pFontName) )
      return FALSE;
   font_desc = pango_font_description_from_string(pFontName);

  /*
   * Change the font style.
   * Just return if no font matches the specified font name.
   */
   pStyle = gtk_style_copy( gtk_widget_get_style( pWidget ) );
g_return_val_if_fail( pStyle, FALSE );

   pStyle->font_desc = font_desc;
   gtk_widget_set_style( pWidget,
                         pStyle );

//   pango_font_description_free(font_desc);

   return TRUE;
}

/*****************************************************************************
 * Apply the fonts in all existing list widgets.
 * @param   fontNameList (GLOBAL)
 *          Font name applied to all list widgets.
 *****************************************************************************/
void
ApplyFontListWidgets( void )
{
   if ( fontNameList == NULL  ||  fontNameList[0] == '\0' )
      return;

   ChangeStyleFont( pWidgetClistTags,    fontNameList );
   ChangeStyleFont( pWidgetClistModules, fontNameList );
   ChangeStyleFont( pWidgetClistHistory, fontNameList );
   if ( pWidgetClistFound ) /* absent if no search done yet */
      ChangeStyleFont( pWidgetClistFound,   fontNameList );
}

/*****************************************************************************
 * Get the average height (in pixels) of a font.
 *****************************************************************************/
gint
FontHeight( PangoFontDescription* pFont )
{
//   static const char str[] = "(|/\\!AabcZzqgj,.";

g_return_val_if_fail( pFont, 0 );

//   g_print("font_point: %d", pango_font_description_get_size(pFont));// return points, but require pixel
   return 8;//13;
//   return gdk_text_height( pFont,
//                           str,
//                           sizeof(str) );
}

/*****************************************************************************
 * Position a widget at middle of main widget.
 *****************************************************************************/
void
PositionWidgetAtMiddle( GtkWidget* pWidget )
{
   gint		x_pos  = 100;		/* default X */
   gint		y_pos  = 100;		/* default Y */

   // receive the main window's position
   gdk_window_get_position( pWidgetMain->window,
//   gtk_window_get_position( GTK_WINDOW(pWidgetMain),
                            &x_pos,
                            &y_pos );
   gtk_widget_set_uposition( pWidget,
                             x_pos + DIALOG_X_OFFSET,
                             y_pos + DIALOG_Y_OFFSET );

   return;
}

/*****************************************************************************
 * Position a widget near top/middle of main widget.
 *****************************************************************************/
void
PositionWidgetAtTopMiddle( GtkWidget* pWidget )
{
   gint		x_pos  = 100;		/* default X */
   gint		y_pos  =  8;		/* default Y */

   gdk_window_get_position( pWidgetMain->window,
                            &x_pos,
                            &y_pos );
   gtk_widget_set_uposition( pWidget,
                             x_pos + DIALOG_X_OFFSET,
                             y_pos + 8 );

   return;
}

/*****************************************************************************
 * Position/show dialog at middle of main widget.
 *****************************************************************************/
void
PositionShowDialogAtMiddle( GtkWidget* pWidgetDialog )
{
   PositionWidgetAtTopMiddle( pWidgetDialog );
   gtk_widget_show_all( pWidgetDialog );

   return;
}

/*****************************************************************************
 * Position/show a find dialog widget.
 *
 * @param   pWidgetDialog
 * @param   dialogPlacement (GLOBAL)
 *          Determines how dialog is placed (DIALOG_PLACEMENT_*).
 *
 * @remarks @verbatim
 * Workaround for a GTK+ quirk:
 * Calling gtk_widget_set_uposition() before a widget is shown
 * results in the widget being at a Y co-ord that is slightly
 * greater than what was intended.  A workaround is to again call
 * gtk_widget_set_uposition() after the widget is shown.
 * 
 * In order to successfully get the size of the dialog widget,
 * it must be showing, but a dilemma is that it hasn't been positioned yet.
 * Just position the widget off-screen (to avoid widget flicker).
 * 
 * On Red Hat 8, only the code for top-corner, middle, and window manager default
 * are capable of positioning the dialog without losing the focus.
 * @endverbatim
 *****************************************************************************/
void
PositionShowFindDialog( GtkWidget* pWidgetDialog )
{
   gint		x_pos	= -1;		/* if these stay unchanged then a gdk call failed */
   gint		y_pos	= -1;
   gint		wMain	= -1;
   gint		hMain	= -1;
   gint		wDialog	= -1;
   gint		hDialog	= -1;

  /*
   * Note:
   * On Red Hat 8, only the code for top-corner, middle, and window manager default
   * are capable of positioning the dialog without losing the focus.
   */

  /*
   * Place dialog at top corner?
   */
   if ( dialogPlacement == DIALOG_PLACEMENT_TOP_CORNER )
   {
      gdk_window_get_position( pWidgetMain->window,
                               &x_pos,
                               &y_pos );
      if ( x_pos == -1 || y_pos == -1 ) goto failsafe;
      gtk_widget_set_uposition( GTK_WIDGET(pWidgetDialog),
                                x_pos,
                                y_pos );
      gtk_widget_show_all( pWidgetDialog );
      return;
   }

  /*
   * Place dialog at bottom corner?
   * --On Red Hat 8, this code causes dialog to lose focus--
   */
   if ( dialogPlacement == DIALOG_PLACEMENT_BOTTOM_CORNER )
   {
     /*
      * Which corner partly depends on whether text widgets are placed on left/right side.
      */
      gdk_window_get_position( pWidgetMain->window,
                               &x_pos,
                               &y_pos );
      if ( x_pos == -1 || y_pos == -1 ) goto failsafe;

     /*
      * Position dialog near a corner.
      */
      gtk_widget_set_uposition( pWidgetDialog,					/* this step is a KLUDGE (see notes) */
                                !textLeft  ?  x_pos  :  5000,	/* 5000 is a KLUDGE (see notes) */
                                !textLeft  ?  y_pos  :  5000 );
      gtk_widget_show_all( pWidgetDialog );						/* now show dialog */

      gdk_window_get_size( pWidgetMain->window,					/* get size of main and dialog */
                           &wMain,
                           &hMain );
      gdk_window_get_size( pWidgetDialog->window,
                           &wDialog,
                           &hDialog );
      if ( wMain==-1 || hMain==-1 || wDialog==-1 || hDialog==-1 ) goto failsafe;

      if ( dialogPlacement == DIALOG_PLACEMENT_BOTTOM_CORNER )	/* add to Y for bottom corner */
         y_pos += hMain - hDialog - 48;							/* slightly move up to avoid obscuring statusbar */

      gtk_widget_set_uposition( pWidgetDialog,					/* really set position */
                                !textLeft  ?  x_pos  :  x_pos+wMain-wDialog,
                                y_pos );

      KludgeForWidgetGeom( pWidgetDialog,
                           !textLeft  ?  x_pos  :  x_pos+wMain-wDialog,
                           y_pos );

      return;
   }

  /*
   * Place dialog at middle?
   */
   if ( dialogPlacement == DIALOG_PLACEMENT_MIDDLE )
   {
      PositionShowDialogAtMiddle( pWidgetDialog );
      return;
   }

  /*
   * Restore previous placement of dialog?
   * --On Red Hat 8, this code causes dialog to lose focus--
   */
   if (    dialogPlacement == DIALOG_PLACEMENT_RESTORE
        && findGeom[0] > 0
        && findGeom[1] > 0 )
   {
      int	x, y;

     /*
      * Ensure dialog won't be placed off-screen.
      */
      x = findGeom[0];
      y = findGeom[1];
      if ( x < MIN_DIALOG_GEOM_X ) x = MIN_DIALOG_GEOM_X;
      if ( y < MIN_DIALOG_GEOM_X ) y = MIN_DIALOG_GEOM_Y;

     /*
      * Restore previous position of dialog.
      */
      gtk_widget_set_uposition( pWidgetDialogFind,
                                x,
                                y );
#if 0
     /* Skip restoring size because size of dialog varies when searching for text, tags, etc. */
      gtk_widget_set_usize( pWidgetDialogFind,
                            findGeom[2],
                            findGeom[3] );
#endif
     /*
      * Workaround GTK+ quirk (see above Notes).
      */
      gtk_widget_show_all( pWidgetDialogFind );
      gtk_widget_set_uposition( pWidgetDialogFind,
                                x,
                                y );
     return;
   }

  /*
   * Default is to let the window manager place the dialog.
   */
place_default:
   gtk_widget_show_all( pWidgetDialog );
   return;

failsafe:
   Warning( "A gdk call failed in %s().", __FUNCTION__ );
   goto place_default;
}

/*****************************************************************************
 * @return The width/height of the window of a widget.
 * (Evidently there is no GTK function to do so?)
 * @param   pWidget
 * @param   pWidth (OUT)
 * @param   pHeight (OUT)
 *****************************************************************************/
void
WidgetWidthHeight( GtkWidget* pWidget,
                   gint*      pWidth,
                   gint*      pHeight )
{
   gint		x;
   gint		y;
   gint		depth;

   gdk_window_get_geometry( pWidget->window,
                            &x,
                            &y,
                            pWidth,
                            pHeight,
                            &depth );

   return;
}

/*****************************************************************************
 * KLUDGE:
 * So that widget positions can be properly saved/restored, this function
 * tries to indirectly determine the effect of border decorations by
 * comparing (x,y) positions of a parent and child widget.
 * The result is a flag to tell WidgetGeom() which position function to call.
 * This kludge is necessary because GTK+ 1.2.8 lacks a single reliable
 * means to set/get the position of a widget independently of window manager.
 * @param   pWidget
 *          (Pass pWidgetFindDialog).
 * @param   x, y
 *          Pass the desired position of widget (pWidget).
 * @param   kludgeWidgetGeomUseGetPosition (OUT/GLOBAL)
 *          TRUE  if gdk_window_get_position()    should be called by WidgetGeom().
 *          FALSE if gdk_window_get_root_origin() should be called by WidgetGeom().
 *****************************************************************************/
void
KludgeForWidgetGeom( GtkWidget* pWidget,
                     int x,
                     int y )
{
   int x_dial;
   int y_dial;
   int x_main;
   int y_main;

g_return_if_fail( pWidget  &&  pWidget->window );

   ProcessPendingEvents();
   gdk_window_get_root_origin( pWidget->window,
                               &x_dial,
                               &y_dial );
   gdk_window_get_root_origin( pWidgetMain->window,
                               &x_main,
                               &y_main );
   ProcessPendingEvents();

   kludgeWidgetGeomUseGetPosition = ( y_dial == y_main );


#ifdef HYPERSRC_DEBUG
   printf( "desired  x = %d y = %d \n", x, y );
   printf( "dial     x = %d y = %d \n", x_dial, y_dial );
   printf( "main     x = %d y = %d \n", x_main, y_main );
#endif

   return;
}

/*****************************************************************************
 * Return the geom (x:y:w:h) of the window of a widget.
 * (Evidently there is no GTK function to do so?)
 * @param   pWidget
 * @param   pX (OUT)
 * @param   pY (OUT)
 * @param   pW (OUT)
 *          width
 * @param   pH (OUT)
 *          height
 *****************************************************************************/
void
WidgetGeom( GtkWidget* pWidget,
            gint*      pX,
            gint*      pY,
            gint*      pW,
            gint*      pH )
{
  /*
   * Compute x:y
   */
   if ( kludgeWidgetGeomUseGetPosition )
   {
     /*
      * This is correct on icewm, incorrect on KDE.
      */
      gdk_window_get_position( pWidget->window,
                               pX,
                               pY );
   }
   else
   {
     /*
      * This is correct on KDE, incorrect on icewm.
      */
      gdk_window_get_root_origin( pWidget->window,
                                  pX,
                                  pY );
   }

  /*
   * Compute w:h
   */
   WidgetWidthHeight( pWidget,
                      pW,
                      pH );

   return;
}

/*****************************************************************************
 * Prepend the string the user typed in a combo widget into a glist.
 * The glist is used by the combo's drop-down widget.
 * @param   pString
 *          String which user entered (a copy is used here).
 * @param   ppGlist (IN/OUT)
 *          Pointer to glist for combo widget drop-down.
 *****************************************************************************/
void
PrependComboDropDown( char*   pStr,
                      GList** ppGList )
{
  /*
   * Only prepend if this string is not same as previous,
   * to avoid populating drop-down with same string when
   * user was repeatedly clicking the Find button.
   *
   * The previous is the first element in the list,
   * but the list might be empty.
   */
   if (    (*ppGList != NULL)
        && (CompareStrings( pStr, (*ppGList)->data ) == 0) )
      return;

  /*
   * Store a persistent clone of user's string (not the original) in the glist.
   */
   *ppGList = g_list_prepend( *ppGList,
                              CloneString( pStr ) );

  /*
   * Remove the last (oldest) element if the list has become too long.
   */
   if ( g_list_length( *ppGList ) > MAX_DROP_DOWN_STRINGS )
   {
     /*
      * Note that g_list_remove() won't always work as expected,
      * because if the last string is duplicated, then non-last
      * duplicate will be removed because it is before the last.
      */
      *ppGList = \
      g_list_remove_link( *ppGList,
                          g_list_last( *ppGList ) );
   }

   return;
}

/*****************************************************************************
 * Create a button with a label with underlined chars.
 * (GTK has no such function).
 *****************************************************************************/
GtkWidget*
CreateButtonLabelUnderlined( const gchar* pLabelText,
                             const gchar* pLabelPattern )
{
   GtkWidget* label_widget;
   GtkWidget* pWidgetButton;

  /*
   * Derived from gtk_button_new_with_label().
   */
  pWidgetButton = gtk_button_new ();
  label_widget = gtk_label_new ( pLabelText );
  gtk_label_set_pattern( GTK_LABEL(label_widget), pLabelPattern );
  gtk_misc_set_alignment (GTK_MISC (label_widget), 0.5, 0.5);
  gtk_container_add (GTK_CONTAINER (pWidgetButton), label_widget);
  gtk_widget_show (label_widget);

  return pWidgetButton;
}

/*****************************************************************************
 * Try to reset functions (for attempting crash recovery).
 *****************************************************************************/
void
ResetFunctions( void )
{
  /*
   * Clear busy flags of major functions.
   */
   busy_FindStringGlobal			=
   busy_FindDialogClickedFind		=
   busy_PopulateTagsClistWidget		=
   busy_GotoTagUnderCursor			=
   busy_FindMatchesUnderCursor		=
   busy_SearchClistWidget_tag		=
   busy_SearchClistWidget_module	=
   busy_StepHistoryList				=
   busy_PropertyPageApply			= FALSE;

  /*
   * Clear busy flags of other functions.
   */
   busy_UpdateFuncTree						=
   busy_HyperjumpViaTreeNodeOrItem_cb		=
   busy_HyperjumpToModuleViaModuleTree_cb	=
   busy_HandlerFuncDefTreeExpand_cb			=
   busy_HandlerModuleTreeExpand_cb			= FALSE;

   ResetTrees();

   return;
}

/*****************************************************************************
 * @return True if any of the major outer-level functions are busy.
 *****************************************************************************/
gboolean
IsAnyMajorFunctionBusy( gboolean showMsg )
{
   gboolean res;

   res =     busy_FindStringGlobal
          || busy_FindDialogClickedFind
          || busy_PopulateTagsClistWidget
          || busy_GotoTagUnderCursor
          || busy_FindMatchesUnderCursor
          || busy_SearchClistWidget_tag
          || busy_SearchClistWidget_module
          || busy_StepHistoryList
          || busy_PropertyPageApply
          ;

   if ( showMsg  &&  res ) StatusbarBusy();

   return res;
}

/*****************************************************************************
 * @return True if any of the major tree functions are busy.
 *****************************************************************************/
gboolean
IsAnyTreeFunctionBusy( void )
{
   gboolean res;

   res =     busy_UpdateFuncTree
          || busy_HyperjumpViaTreeNodeOrItem_cb
          || busy_HyperjumpToModuleViaModuleTree_cb
          || busy_HandlerFuncDefTreeExpand_cb
          || busy_HandlerModuleTreeExpand_cb
          ;

   return res;
}

/*****************************************************************************
 * For while busy, set sensitivity of menu items and toolbar buttons.
 * For consistency/simplicity, this should be called once at the outer level
 * of a major function, rather than being nested.
 *****************************************************************************/
void
SetSensitivityWhileBusy( gboolean sensitive )
{
  /*
   * In case this instance is nested, do nothing.
   */
   if ( ! sensitive )
   {
      ++SetSensitivityWhileBusy_cnt;
      if ( SetSensitivityWhileBusy_cnt < 0 ) WarningMalfunction();
      if ( SetSensitivityWhileBusy_cnt != 1 ) return;
   }
   else
   {
      --SetSensitivityWhileBusy_cnt;
      if ( SetSensitivityWhileBusy_cnt < 0 ) WarningMalfunction();
      if ( SetSensitivityWhileBusy_cnt != 0 ) return;
   }

  /*
   * Menu items.
   */
   SetSensitivityMenubar( sensitive );

  /*
   * Toolbar buttons.
   */
g_return_if_fail( pWidgetToolbar );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonGotoLine),     sensitive );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonGotoTag),      sensitive );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonEarlierTag),   sensitive );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonLaterTag),     sensitive );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonFindMatches),  sensitive );
   gtk_widget_set_sensitive( GTK_WIDGET(pWidgetToolbarButtonRecordCursor), sensitive );

  /*
   * Trees.
   */
   SetSensitivityModuleFuncTrees( sensitive, HIDE_IF_INSENSITIVE );

   return;
}

/*****************************************************************************
 * Set the initial positions of the paned sliders (there are 3).
 *****************************************************************************/
void
SetPanedSlidersInitialPositions( void )
{
   int		w, h;

  /*
   * ---For the vertical paned slider between clist and text widgets---
   *
   * If the position wasn't saved in the GNOME config,
   * then compute a default position.
   * (A wrong w/h will be returned if windows are not yet shown.)
   */
   WidgetWidthHeight( pWidgetMain, &w, &h );
   if ( panedSliderPercent == 0 )
      panedSliderPercent = textLeft ? 60 : 35;
   gtk_paned_set_position( GTK_PANED(pWidgetPaned), w * panedSliderPercent / 100 );

  /*
   * ---For the horizontal paned slider near the functree---
   */
   if ( ! skipFuncTree )
   {
      if ( panedSliderFuncTreePercent == 0 ) 
           panedSliderFuncTreePercent = DEFAULT_PANED_SLIDER_PERCENT_FUNC_TREE;
      /* this works, see PanedSliderPercent() */
      WidgetWidthHeight( pWidgetScrolledFuncTree, &w, &h );
      gtk_paned_set_position( GTK_PANED(pWidgetPanedFuncTree),
                              h * panedSliderFuncTreePercent / 100 );
   }

  /*
   * ---For the paned slider between the main/aux text widgets---
   */
   WidgetWidthHeight( pWidgetPanedText, &w, &h );
   if ( panedSliderTextPercent == 0 )
      panedSliderTextPercent = 50;
   gtk_paned_set_position( GTK_PANED(pWidgetPanedText),
                           h * (panedSliderTextPercent + 6) / 100 );
                            /* KLUDGE: +6 is a magic displacement */

   return;
}

/*****************************************************************************
 * Return the percentage of the movement of a paned slider.
 * For the clist/text slider: 0% = left.
 * @param   pWidget
 *          Pointer to a paned slider.
 * @return  An int in range: {0..99}
 *****************************************************************************/
int
PanedSliderPercent( GtkWidget* pWidget )
{
   int		geom[4];
   int		geom2[4];
   float	f1 = 0.0, f2 = 0.0;
   int		percent = 0;

   if ( pWidget == pWidgetPaned )
   {
      WidgetGeom( pWidgetNotebook,
                  &geom[0], &geom[1], &geom[2], &geom[3] );
      WidgetGeom( pWidgetTextActive,
                  &geom2[0], &geom2[1], &geom2[2], &geom2[3] );
      if ( ! textLeft )
      {
         f1 = geom[2];
         f2 = geom2[2];
      }
      else
      {
         f1 = geom2[2];
         f2 = geom[2];
      }

      percent = (f1 / (f1 + f2)) * 100.0;
   }
   else if ( pWidget == pWidgetPanedFuncTree )
   {
      if ( skipFuncTree )
      {
         DebugMsg( "PanedSliderPercent() called for non-existent functree, faking it." );
         return DEFAULT_PANED_SLIDER_PERCENT_FUNC_TREE;
      }

     /*
      * KLUDGE:
      * For whatever reason, the height reported by pWidgetScrolledFuncTree
      * won't changed as the slider is moved up/down.
      * It height is used as though it was the total height of the vertical paned widget.
      */
      WidgetGeom( pWidgetNotebook,
                  &geom[0], &geom[1], &geom[2], &geom[3] );
      WidgetGeom( pWidgetScrolledFuncTree,
                  &geom2[0], &geom2[1], &geom2[2], &geom2[3] );
      f1 = geom[3];
      f2 = geom2[3];

      percent = f1 / f2 * 100.0;
   }
   else if ( pWidget == pWidgetPanedText )
   {
      WidgetGeom( pWidgetTextMain,
                  &geom[0], &geom[1], &geom[2], &geom[3] );
      WidgetGeom( pWidgetPanedText,
                  &geom2[0], &geom2[1], &geom2[2], &geom2[3] );
      f1 = geom[3];
      f2 = geom2[3];

      percent = f1 / f2 * 100.0;
   }
   else
      WarningMalfunction();

   if ( percent < 0  ||  percent > 100 )
   {
      DebugMsg( "Slider percent miscomputed, defaulting." );
      percent = 25;
   }

   return percent;
}

/*****************************************************************************
 * Process all pending GTK (and/or X) events.
 *
 * This is useful to serialize the drawing of widgets.
 * But this should be used sparingly because (I think) it can confuse GTK+
 * in some situations (eg further events will be pulled from the queue
 * while one event is still be processed by hypersrc).
 *
 * @remarks If ProcessPendingEvents() is disabled, then a detriment will be
 *          that HighlightWordInCurrentLine() usually won't work (see its notes).
 *****************************************************************************/
void
ProcessPendingEvents( void )
{
   if ( ! enableProcessPendingEvents ) return;

   while ( gtk_events_pending() )
   {
      gtk_main_iteration();
   }

   return;
}

/*****************************************************************************
 * Periodically call ProcessPendingEvents().
 *****************************************************************************/
void
PeriodicallyProcessPendingEvents( void )
{
#define PROCESS_EVENTS_INTERVAL	10 //4 /*0xff*/
   static int cnt = 0;

   if ( cnt++ >= PROCESS_EVENTS_INTERVAL )
   {
      cnt = 0;
      ProcessPendingEvents();
   }
}
